/*
	File:		Input_Win32.h
	Author:		dmonroe, Arturo Mata
	Date:		11/10/2012
	Copyright:	Full Sail University

	Purpose:	Win32 input implementation using XInput
*/

// Only compile on Win32
#if defined( _WIN32 )

#include "../source/IInput.h"


#if defined( _DEBUG ) || defined( DEBUG )
#if defined( _WIN32 ) || defined( WIN32 )

#include <Windows.h>


#endif // defined( _WIN32 ) || defined( WIN32 )
#endif // defined( _DEBUG ) || defined( DEBUG )


// TODO: XInput include should point to the June 2010 DirectX SDK version,
//		 not the one in the Microsoft SDK (Windows/v7.0A/Include)
#include <XInput.h>
#pragma comment( lib, "XInput.lib" )

#include <cassert>


#include "../source/Constants.h"
#include "../source_Win32/KeyboardAndMouse.h"


#define JOYSTICK_DEADZONE 0.25f
#define NUM_GAMEPADS 4

//******************************************************************
// CInput
//	- concrete class for handling input on Win32
//	- creates / handles xinput
//	- returns button states
class CInput : public IInput
{
public:
	// SINGLETON helper:
	friend	IInput*		IInput::GetInstance		( void );
	friend	void		IInput::DeleteInstance	( void );
	
	// SETUP
	virtual	bool		Initialize		( void );
	virtual	bool		Terminate		( void );
	
	// FRAME UPDATE
	virtual bool		Update			( void );

	// TODO: IInput's accessors
	void		SetInputStatus			( bool _bEnable );
	bool		ConnectionStatus		( int _nControllerID = 0 );
	bool		ButtonPressed			( EButton _eButton, int _nControllerID = 0 );
	bool		IsButtonDown			( EButton _eButton, int _nControllerID = 0 );
	bool		IsButtonUp				( EButton _eButton, int _nControllerID = 0 );
	float		LStickX					( int _nControllerID = 0 );
	float		LStickY					( int _nControllerID = 0 );
	float		RStickX					( int _nControllerID = 0 );
	float		RStickY					( int _nControllerID = 0 );
	int			LTrigger				( int _nControllerID = 0 );
	int			RTrigger				( int _nControllerID = 0 );
	void		Rumble					( unsigned short _LeftMotor, unsigned short _RightMotor, int _nControllerID = 0 );
	int			BatteryStatus			( int _nControllerID = 0 );


private:
	// SINGLETON
	static CInput*		s_Instance;	

	CInput				( void );				// Default constructor
	virtual	~CInput		( void );				// Destructor

	CInput				( const CInput& );		// Copy constructor
	CInput&	operator=	( const CInput& );		// Assignment operator

	
	// Wrapper Status
	enum EWrapperStatus
	{	
		E_UNINITIALIZED,
		E_INITIALIZED,
		E_DESTROYED
	};

	EWrapperStatus			m_eStatus;			// wrapper initialization status

	// Array of API specific values used in checking against the API's return values.
	// The EButton enumeration should be used to access into the array.
	unsigned short					m_usButtonValues[ E_NUM_BUTTONS ];

	// The gamepad states used for buffered input.
	XINPUT_STATE					m_xiPrevGamepadStates[ NUM_GAMEPADS ];
	XINPUT_STATE					m_xiCurrGamepadStates[ NUM_GAMEPADS ];

	// Gamepad connection status
	DWORD							m_dwGamepadStatus[ NUM_GAMEPADS ];


	// Array of Win32 API values mapped to mimic a controller
	unsigned char					m_ucKeyboardValues[ E_NUM_BUTTONS ];

	// The keyboard states used for buffered input
	XINPUT_STATE					m_xiPrevKeyboardState;
	XINPUT_STATE					m_xiCurrKeyboardState;

	// TODO: Action mapping -> MENU_ACCEPT, MENU_BACK, etc.
};
//******************************************************************



//******************************************************************
// SINGLETON

// Instantiate static pointer to null (no instance yet)
/*static*/ CInput*		CInput::s_Instance		= NULL;

// Interface singleton accessor
/*static*/ IInput*		IInput::GetInstance		( void )
{
	// Allocate singleton on first use
	if( CInput::s_Instance == NULL )
		CInput::s_Instance = new CInput;

	// Return the singleton
	return CInput::s_Instance;
}

// Interface singleton destructor
/*static*/ void			IInput::DeleteInstance	( void )
{
	// Deallocate singleton
	delete CInput::s_Instance;
	CInput::s_Instance = NULL;
}
//******************************************************************


//******************************************************************
// CONSTRUCTOR
CInput::CInput( void )
{
	// Set data members to 'clean' values
	m_eStatus = E_UNINITIALIZED;

	// Initialize the Button Values based on the API values.
	m_usButtonValues[	E_DPAD_UP			]	= XINPUT_GAMEPAD_DPAD_UP;
	m_usButtonValues[	E_DPAD_DOWN			]	= XINPUT_GAMEPAD_DPAD_DOWN;
	m_usButtonValues[	E_DPAD_LEFT			]	= XINPUT_GAMEPAD_DPAD_LEFT;
	m_usButtonValues[	E_DPAD_RIGHT		]	= XINPUT_GAMEPAD_DPAD_RIGHT;
	m_usButtonValues[	E_FACE_UP			]	= XINPUT_GAMEPAD_Y;
	m_usButtonValues[	E_FACE_DOWN			]	= XINPUT_GAMEPAD_A;
	m_usButtonValues[	E_FACE_LEFT			]	= XINPUT_GAMEPAD_X;
	m_usButtonValues[	E_FACE_RIGHT		]	= XINPUT_GAMEPAD_B;
	m_usButtonValues[	E_LEFT_SHOULDER		]	= XINPUT_GAMEPAD_LEFT_SHOULDER;
	m_usButtonValues[	E_RIGHT_SHOULDER	]	= XINPUT_GAMEPAD_RIGHT_SHOULDER;
	m_usButtonValues[	E_LEFT_THUMB 		]	= XINPUT_GAMEPAD_LEFT_THUMB;
	m_usButtonValues[	E_RIGHT_THUMB 		]	= XINPUT_GAMEPAD_RIGHT_THUMB;
	m_usButtonValues[	E_START 			]	= XINPUT_GAMEPAD_START;
	m_usButtonValues[	E_SELECT			]	= XINPUT_GAMEPAD_BACK;

	// Zero out the previous gamepad states.
	ZeroMemory( m_xiPrevGamepadStates, sizeof( m_xiPrevGamepadStates ) );
	ZeroMemory( m_xiCurrGamepadStates, sizeof( m_xiCurrGamepadStates ) );

	// Set the controller status' to disconnected (for now)
	for( int i = 0; i < NUM_GAMEPADS; i++ )
		m_dwGamepadStatus[ i ] = ERROR_DEVICE_NOT_CONNECTED;


	// Map the keyboard button values
	m_ucKeyboardValues[	E_DPAD_UP			]	= 'W';
	m_ucKeyboardValues[	E_DPAD_DOWN			]	= 'S';
	m_ucKeyboardValues[	E_DPAD_LEFT			]	= 'A';
	m_ucKeyboardValues[	E_DPAD_RIGHT		]	= 'D';
	m_ucKeyboardValues[	E_FACE_UP			]	= VK_LSHIFT;		// TODO???
	m_ucKeyboardValues[	E_FACE_DOWN			]	= VK_SPACE;
	m_ucKeyboardValues[	E_FACE_LEFT			]	= VK_TAB;
	m_ucKeyboardValues[	E_FACE_RIGHT		]	= VK_ESCAPE;
	m_ucKeyboardValues[	E_LEFT_SHOULDER		]	= VK_RBUTTON;
	m_ucKeyboardValues[	E_RIGHT_SHOULDER	]	= VK_LBUTTON;		// right-shoulder == common == left-mouse
	m_ucKeyboardValues[	E_LEFT_THUMB 		]	= VK_XBUTTON2;		// who presses the sticks???
	m_ucKeyboardValues[	E_RIGHT_THUMB 		]	= VK_XBUTTON1;
	m_ucKeyboardValues[	E_START 			]	= VK_RETURN;
	m_ucKeyboardValues[	E_SELECT			]	= VK_ESCAPE;
	
	// Zero out the previous keyboard states.
	ZeroMemory( &m_xiPrevKeyboardState, sizeof( m_xiPrevKeyboardState ) );
	ZeroMemory( &m_xiCurrKeyboardState, sizeof( m_xiCurrKeyboardState ) );
}
//******************************************************************


//******************************************************************
// DESTRUCTOR
/*virtual*/	CInput::~CInput( void )
{
	// Release memory cleanly in Release mode
#if !defined( DEBUG ) && !defined( _DEBUG )
	if( m_eStatus == E_INITIALIZED )
		Terminate();
#endif
}
//******************************************************************


//******************************************************************
// INITIALIZE
/*virtual*/	bool CInput::Initialize( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_UNINITIALIZED && "CInput::Initialize - wrapper has already been initialized" );

	// TODO: Configure XInput
	SetInputStatus( true );

	// Success!
	m_eStatus = E_INITIALIZED;
	return true;
}
//******************************************************************


//******************************************************************
// TERMINATE
/*virtual*/	bool CInput::Terminate( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CInput::Terminate - wrapper has not been initialized" );

	// TODO: Shutdown XInput

	// Wrapper is now destroyed
	m_eStatus = E_DESTROYED;
	return true;
}
//******************************************************************


//******************************************************************
// UPDATE
//	- Refresh XInput 
/*virtual*/ bool CInput::Update( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CInput::Update - wrapper has not been initialized" );
	
	// Store previous frame
	ZeroMemory( m_xiPrevGamepadStates, sizeof( m_xiPrevGamepadStates ) );
	for( int i = 0; i < NUM_GAMEPADS; i++ )
		m_xiPrevGamepadStates[ i ] = m_xiCurrGamepadStates[ i ];
	
	// Refresh XInput
	ZeroMemory( m_xiCurrGamepadStates, sizeof( m_xiCurrGamepadStates ) );
	for( int i = 0; i < NUM_GAMEPADS; i++ )
		m_dwGamepadStatus[ i ] = XInputGetState( 3, &m_xiCurrGamepadStates[ i ] );


	// Store previous keyboard
	ZeroMemory( &m_xiPrevKeyboardState, sizeof( m_xiPrevKeyboardState ) );
	m_xiPrevKeyboardState = m_xiCurrKeyboardState;

	// Refresh keyboard & mouse state
	SKeyboardState	keyboard;
	SMouseState		mouse;

	GetKeyboardState( &keyboard );
	GetMouseState( &mouse );
	
	ZeroMemory( &m_xiCurrKeyboardState, sizeof( m_xiCurrKeyboardState ) );
	for( int i = 0; i < E_NUM_BUTTONS; i++ )
		if( keyboard.bKeys[ m_ucKeyboardValues[ i ] ] == true )
			m_xiCurrKeyboardState.Gamepad.wButtons  |= m_usButtonValues[ i ];

	// Should probably scale the mouse movement to a joystick movement ...
	if( mouse.nDeltaX < 0 )
		m_xiCurrKeyboardState.Gamepad.sThumbLX = SHRT_MIN;
	else if( mouse.nDeltaX > 0 )
		m_xiCurrKeyboardState.Gamepad.sThumbLX = SHRT_MAX;
	else
		m_xiCurrKeyboardState.Gamepad.sThumbLX = 0;
	
	if( mouse.nDeltaY < 0 )
		m_xiCurrKeyboardState.Gamepad.sThumbLY = SHRT_MIN;
	else if( mouse.nDeltaY > 0 )
		m_xiCurrKeyboardState.Gamepad.sThumbLY = SHRT_MAX;
	else
		m_xiCurrKeyboardState.Gamepad.sThumbLY = 0;
	
	// Continue to run the application
	return true;
}
//******************************************************************


//******************************************************************
// SET INPUT STATUS
void CInput::SetInputStatus( bool _bEnable )
{
	XInputEnable( _bEnable );
}
//******************************************************************


//******************************************************************
// CONNECTION STATUS
bool CInput::ConnectionStatus( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ConnectionStatus - controller ID is not valid" );
		
	// Return the controller's status
	return (m_dwGamepadStatus[ _nControllerID ] == ERROR_SUCCESS);
}
//******************************************************************


//******************************************************************
// BUTTON PRESSED
/*virtual*/ bool CInput::ButtonPressed( EButton _eButton, int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Keyboard?
	if( _nControllerID == 0 )
	{
		// Make sure it's a valid button enumeration index.
		if( _eButton >= 0 && _eButton < E_NUM_BUTTONS )
		{
			// Check the current state against the previous state.
			int prev = m_xiPrevKeyboardState.Gamepad.wButtons & m_usButtonValues[ _eButton ];
			int cur = m_xiCurrKeyboardState.Gamepad.wButtons & m_usButtonValues[ _eButton ];
			
			// If the previous was down and current is now up, the button was pressed.
			if( prev == m_usButtonValues[ _eButton ] && cur != m_usButtonValues[ _eButton ] )
			{
				return true;
			}
		}
	}

	// Make sure the controller is connected before doing anything.
	if( ConnectionStatus( _nControllerID ) )
	{
		// Make sure it's a valid button enumeration index.
		if( _eButton >= 0 && _eButton < E_NUM_BUTTONS )
		{
			// Check the current state against the previous state.
			int prev = m_xiPrevGamepadStates[ _nControllerID ].Gamepad.wButtons & m_usButtonValues[ _eButton ];
			int cur = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.wButtons & m_usButtonValues[ _eButton ];

			// Make sure we're dealing with a different state packet.
			if( m_xiCurrGamepadStates[ _nControllerID ].dwPacketNumber != m_xiPrevGamepadStates[ _nControllerID ].dwPacketNumber )
			{
				// If the previous was down and current is now up, the button was pressed.
				if( prev == m_usButtonValues[ _eButton ] && cur != m_usButtonValues[ _eButton ] )
				{
					return true;
				}

				// Either the previous was up or the current is down.
				return false;

			}

			// Same packet state.
			return false;

		}

		// Uknown button.
		return false;

	}

	// Controller not connected.
	return false;
}
//******************************************************************


//******************************************************************
// IS BUTTON DOWN	
/*virtual*/ bool CInput::IsButtonDown( EButton _eButton, int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );
	
	// Keyboard?
	if( _nControllerID == 0 )
	{
		// Make sure it's a valid button enumeration index.
		if( _eButton >= 0 && _eButton < E_NUM_BUTTONS )
		{
			// Check the current state.
			int cur = m_xiCurrKeyboardState.Gamepad.wButtons & m_usButtonValues[ _eButton ];
			
			// Check the button's value against its current state.
			if( cur == m_usButtonValues[ _eButton ] )
			{
				return true;
			}
		}
	}

	// Make sure the controller is connected before doing anything.
	if( ConnectionStatus( _nControllerID ) )
	{
		// Make sure it's a valid button enumeration index.
		if( _eButton >= 0 && _eButton < E_NUM_BUTTONS )
		{
			// Check the current state.
			int nCurrentState = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.wButtons & m_usButtonValues[ _eButton ];

			// Check the buttons value against it's current state.
			if( nCurrentState == m_usButtonValues[ _eButton ] )
			{
				return true;
			}

			// The current buttons state is up.
			return false;

		}

		// Uknown button.
		return false;

	}

	// Controller not connected.
	return false;

}
//******************************************************************


//******************************************************************
// IS BUTTON UP
bool CInput::IsButtonUp( EButton _eButton, int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );
	
	// Keyboard?
	if( _nControllerID == 0 )
	{
		// Make sure it's a valid button enumeration index.
		if( _eButton >= 0 && _eButton < E_NUM_BUTTONS )
		{
			// Check the current state.
			int cur = m_xiCurrKeyboardState.Gamepad.wButtons & m_usButtonValues[ _eButton ];
			
			// Check the button's value against its current state.
			if( cur == m_usButtonValues[ _eButton ] )
			{
				return false;
			}
		}
	}

	// Make sure the controller is connected before doing anything.
	if( ConnectionStatus( _nControllerID ) )
	{
		// Make sure it's a valid button enumeration index.
		if( _eButton >= 0 && _eButton < E_NUM_BUTTONS )
		{
			// Check the current state.
			int nCurrentState = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.wButtons & m_usButtonValues[ _eButton ];

			// Check the buttons value against it's current state.
			if( nCurrentState != m_usButtonValues[ _eButton ] )
			{
				return true;
			}

			// The current buttons state is down.
			return false;

		}

		// Uknown button.
		return false;

	}

	// Controller not connected.
	return false;
}
//******************************************************************


//******************************************************************
// L STICK X
float CInput::LStickX( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Verify the controller is connected first.
	if( ConnectionStatus( _nControllerID ) )
	{
		float fReturn = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.sThumbLX / 32767.0f;
		
		// Return 0 if its within the dead zone, otherwise return the joystick value.
		return ( fReturn > JOYSTICK_DEADZONE || fReturn < -JOYSTICK_DEADZONE ) ? fReturn : 0.0f; 
	}
	else
	{
		return 0.0f;
	}
}
//******************************************************************


//******************************************************************
// L STICK Y
float CInput::LStickY( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Verify the controller is connected first.
	if( ConnectionStatus( _nControllerID ) )
	{
		float fReturn = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.sThumbLY / 32767.0f;
		
		// Return 0 if its within the dead zone, otherwise return the joystick value.
		return ( fReturn > JOYSTICK_DEADZONE || fReturn < -JOYSTICK_DEADZONE ) ? fReturn : 0.0f; 
	}
	else
	{
		return 0.0f;
	}

}
//******************************************************************


//******************************************************************
// R STICK X
float CInput::RStickX( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Verify the controller is connected first.
	if( ConnectionStatus( _nControllerID ) )
	{
		float fReturn = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.sThumbRX / 32767.0f;
		
		// Return 0 if its within the dead zone, otherwise return the joystick value.
		return ( fReturn > JOYSTICK_DEADZONE || fReturn < -JOYSTICK_DEADZONE ) ? fReturn : 0.0f; 
	}
	else
	{
		return 0.0f;
	}
}
//******************************************************************


//******************************************************************
// R STICK Y
float CInput::RStickY( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Verify the controller is connected first.
	if( ConnectionStatus( _nControllerID ) )
	{
		float fReturn = m_xiCurrGamepadStates[ _nControllerID ].Gamepad.sThumbRY / 32767.0f;
		
		// Return 0 if its within the dead zone, otherwise return the joystick value.
		return ( fReturn > JOYSTICK_DEADZONE || fReturn < -JOYSTICK_DEADZONE ) ? fReturn : 0.0f; 
	}
	else
	{
		return 0.0f;
	}
}
//******************************************************************


//******************************************************************
// L TRIGGER
int CInput::LTrigger( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Make sure the controller is connected.
	if( ConnectionStatus( _nControllerID ) )
	{
		return m_xiCurrGamepadStates[ _nControllerID ].Gamepad.bLeftTrigger;//(  > XINPUT_GAMEPAD_TRIGGER_THRESHOLD ) ? xiState.Gamepad.bLeftTrigger : 0;
	}
	else
	{
		// Controller is not connected.
		return -1;
	}
}
//******************************************************************


//******************************************************************
// R TRIGGER
int CInput::RTrigger( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::ButtonPressed - controller ID is not valid" );


	// Make sure the controller is connected.
	if( ConnectionStatus( _nControllerID ) )
	{
		return m_xiCurrGamepadStates[ _nControllerID ].Gamepad.bRightTrigger;//(  > XINPUT_GAMEPAD_TRIGGER_THRESHOLD ) ? xiState.Gamepad.bLeftTrigger : 0;
	}
	else
	{
		// Controller is not connected.
		return -1;
	}
}
//******************************************************************


//******************************************************************
// RUMBLE
void CInput::Rumble( unsigned short _LeftMotor, unsigned short _RightMotor, int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::Rumble - controller ID is not valid" );


	// Make sure the controller is connected first.
	if( ConnectionStatus( _nControllerID ) )
	{
		XINPUT_VIBRATION xiVibration;
		ZeroMemory( &xiVibration, sizeof( XINPUT_VIBRATION ) );

		// Set the rumble amounts.
		xiVibration.wLeftMotorSpeed = _LeftMotor;
		xiVibration.wRightMotorSpeed = _RightMotor;

		XInputSetState( _nControllerID, &xiVibration );
	}
}
//******************************************************************


//******************************************************************
// BATTERY STATUS
int CInput::BatteryStatus( int _nControllerID )
{
	// Validate the controller ID
	assert( _nControllerID >= 0 && _nControllerID < NUM_GAMEPADS && "CInput::BatteryStatus - controller ID is not valid" );


	//Query controller (as opposed to headset)
	BYTE byDevice = BATTERY_DEVTYPE_GAMEPAD;
	
	//Create a blank battery info struct
	XINPUT_BATTERY_INFORMATION xiBatteryInfo;
	ZeroMemory( &xiBatteryInfo, sizeof( XINPUT_BATTERY_INFORMATION ) );
	DWORD dwBatteryCharge = XInputGetBatteryInformation( _nControllerID, byDevice, &xiBatteryInfo );

	return xiBatteryInfo.BatteryLevel;

}
//******************************************************************


#endif	// defined( _WIN32 )
